定期ミートアップ 第20回
vtable
多相の実装に使っている
あるメソッド呼び出しが、どのクラスのメソッドなのかわからない(実行時まで決定しない)ケースがある
例
code:sk
puts obj.inspect
Int#inspectなのかString#inspectなのかが静的に決まらない
→実行時に、objの型に応じたllvm funcを呼びだす必要がある
ナイーブな実装
objの属するクラスを調べる→そのクラスのメソッド一覧からinspectを探す
遅い
メソッドを一列に並べて、先頭から番号を振る
code:txt
Object - Monster - Dragon
0 1 8 9 12 13
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
Object由来のメソッド Monster由来のメソッド Dragon固有のメソッド
Dragonクラスでattackを再定義するときは、9番目をMonster#attackではなくDragon#attackにする
こうすると、あるオブジェクトのattackメソッドを実行するには
1. そのオブジェクトのクラスを知る
2. そのクラスのvtableを取得
3. vtableの9番目のllvm funcを実行
とすることで高速に実行できる
Moduleとvtable
ShiikaのModuleは、RubyのModule・SwiftのProtocol・Rustのtraitに相当
継承関係とは独立した仕組み
vtableでは実現できない
(2022/04/24追記) たとえば、モジュールのメソッドは先頭に置く、としたらどうなるか
code:txt
class A : Enumerable
class B : Comparable
ここまではいいが、両方を実装したクラスがあったら?
code:txt
class C : Enumerable, Comparable
# こうするとAと矛盾するし
# こうするとBと矛盾する
Swiftのprotocol
protocolのためにwitness tableという仕組みがあるらしい
それを参考に
例:
code:sk
module Enumerable<T>
# Enumerableモジュールが要求するメソッド(構文要検討)
def each<U>(f: Fn1<T, U>)
# eachを使ったメソッド
def all?(f: Fn1<T, Void>) -> Bool
var b = true;
self.each{|x| b = false if f(x)}
b
end
end
code:llvm
define %Bool* "Enumerable#all?"(%Object* self, %Fn1* f)
{
// self.eachを呼ぶにはどうしたらいいか
}
define %Bool* "Enumerable#all?"(%Object* self, %Fn1* f)
{
// self.eachを呼ぶにはどうしたらいいか
}
wrapped = [
call "Enumerable#all?"(wrapped, f)
%Object = type { i8*, %Class.0* }
%Int = type { i8*, %Class.0*, i64 }
%String = type { i8*, %Class.0*, %"Shiika::Internal::Ptr"*, %Int* }
witness table
code:txt
class Arrayのwitness table
- Enumerableモジュールについて、
- eachの本体はこのllvm func
- XXモジュールについて、
- method1の本体は...
- method2の本体は...
あるオブジェクトのeachメソッドを実行するとき
1. そのオブジェクトのクラスを知る
2. そのクラスのwitness tableのEnumerableの行を探す
3. そこからeachを探す
4. 実行
速度改善
「Enumerableの行を探す」をどうやるか
文字列だと遅そう
::Enumerableを使う
dyn trait A + B
[[a1, a2, ..., b1, b2, ...], obj]